﻿using System;
using System.Collections.Generic;
using System.Linq;

using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Events;
using mvcForum.Core.Interfaces.AddOns;
using mvcForum.Core.Interfaces.Events;
using mvcForum.Core.Specifications;
using SimpleLocalisation;
using mvcForum.Core.Interfaces.Services;
using mvcForum.Core.Interfaces.Data;


namespace mvcForum.AddOns.NewUserTrial {

	public class NewUserTrialAddOn : IAntiSpamAddOn, IEventListener<NewPostEvent>, IEventListener<NewTopicEvent> {
		private readonly NewUserTrialConfiguration addOnConfig;
		private readonly ILogger logger;
		private readonly IRepository<Post> postRepo;
		private readonly IRepository<Topic> topicRepo;
		private readonly IRepository<GroupMember> gmRepo;
		private readonly IContext context;
		private readonly IAsyncTask task;
		private readonly TextManager textManager;

		public NewUserTrialAddOn(NewUserTrialConfiguration addOnConfig, ILogger logger, IRepository<Post> postRepo, IContext context, IAsyncTask task, IRepository<Topic> topicRepo, IRepository<GroupMember> gmRepo, TextManager textManager) {
			this.addOnConfig = addOnConfig;
			this.logger = logger;
			this.postRepo = postRepo;
			this.context = context;
			this.task = task;
			this.topicRepo = topicRepo;
			this.gmRepo = gmRepo;
			this.textManager = textManager;
		}

		public void Handle(Object payload) {
			if (this.addOnConfig.Enabled) {
				if (payload is NewTopicEvent) {
					this.Handle((NewTopicEvent)payload);
				}
				else if (payload is NewPostEvent) {
					this.Handle((NewPostEvent)payload);
				}
				else {
					throw new ApplicationException("Unknown payload!");
				}
			}
		}

		public void Handle(NewTopicEvent payload) {
			if (this.addOnConfig.Enabled) {
				Topic topic = this.topicRepo.Read(payload.TopicId);
				// Not already deleted or quarantined? Should the new topic be put in quarantine?
				if ((topic.Flag & TopicFlag.Deleted) == 0 &&
					(topic.Flag & TopicFlag.Quarantined) == 0 &&
					this.ShouldQuarantine(topic.Author, false, topic.Id, payload)) {

					// Yes, let's mark it quarantined!
					topic.SetFlag(TopicFlag.Quarantined);

					this.MarkAsSpam(topic.Posts.OrderBy(p => p.Posted).First(), payload	);

					this.context.SaveChanges();
				}
			}
		}

		public void Handle(NewPostEvent payload) {
			if (this.addOnConfig.Enabled) {
				Post post = this.postRepo.Read(payload.PostId);
				// Not already deleted or quarantined? Should the new post be put in quarantine?
				if ((post.Flag & PostFlag.Deleted) == 0 &&
					(post.Flag & PostFlag.Quarantined) == 0 &&
					this.ShouldQuarantine(post.Author, true, post.Id, payload)) {

					// Yes, let's mark it quarantined!
					post.SetFlag(PostFlag.Quarantined);

					this.MarkAsSpam(post, payload);

					this.context.SaveChanges();
				}
			}
		}

		private void MarkAsSpam(Post post, InformationBearingEvent payload) {
			post.EditReason += Environment.NewLine + this.textManager.Get("QuarantineReason", ns: "mvcForum.AddOns.NewUserTrial", values: new { Limit = this.addOnConfig.AutoLimit });

			payload.AddFeedback("QuarantineFeedback", "mvcForum.AddOns.NewUserTrial", null);
		}

		private bool ShouldQuarantine(ForumUser author, Boolean checkPost, Int32 id, InformationBearingEvent e) {
			Boolean quarantine = false;
			// Auto quarantine new users?
			if (this.addOnConfig.AutoLimit > 0) {
				// Yes!
				Boolean excluded = false;
				// Any groups excluded from this??
				if (this.addOnConfig.ExcludeGroups.Count() > 0) {
					// Get all groups this user is member of!
					IEnumerable<GroupMember> gms = this.gmRepo.ReadMany(new GroupMemberSpecifications.SpecificUser(author));
					foreach (GroupMember gm in gms) {
						// Is this user member of one of the excluded groups??
						if (this.addOnConfig.ExcludeGroups.Contains(gm.Group.Id)) {
							// No need to check this user, he's in an excluded group!
							excluded = true;
							break;
						}
					}
				}

				if (!excluded) {
					Int32 posts = 0;
					if (checkPost) {
						posts = this.postRepo.ReadMany(new PostSpecifications.AllVisibleByAuthor(author)).Where(p => p.Id != id).Count();
					}
					else {
						posts = this.topicRepo.ReadMany(new TopicSpecifications.AllVisibleByAuthor(author)).Where(t => t.Id != id).Count();
					}
					quarantine = posts < this.addOnConfig.AutoLimit;
					if (quarantine) {
						e.AddFeedback("QuarantineReason", "mvcForum.AddOns.NewUserTrial", new { Posts = posts, Limit = this.addOnConfig.AutoLimit });
					}
				}
			}
			return quarantine;
		}

		public Boolean RunAsynchronously {
			get {
				return false;
			}
		}

		public Byte Priority {
			get {
				return (Byte)EventPriority.Medium;
			}
		}

		public Boolean UniqueEvent {
			get {
				return false;
			}
		}
	}
}